import 'dart:io';

import 'package:appcheck/appcheck.dart';
import 'package:get/get.dart';
import 'package:health/health.dart';
import 'package:launch_review/launch_review.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_fit/app/controllers/space/service/health_state.dart';
import 'package:share_fit/app/helper/db/ute_db_helper.dart';
import 'package:share_fit/app/helper/ext/tr_ext.dart';
import 'package:share_fit/app/helper/storage_helper.dart';
import 'package:share_fit/app/models/home/heart_rate_model.dart';
import 'package:share_fit/app/models/home/spo2_model.dart';
import 'package:share_fit/app/models/home/step_model.dart';
import 'package:share_fit/app/tools/color_const.dart';
import 'package:share_fit/app/tools/date_tool.dart';
import 'package:share_fit/app/tools/log_utils.dart';
import 'package:share_fit/app/ute/uteped_const.dart';
import 'package:share_fit/generated/locales.g.dart';

class HealthHelper {

  static const healthAuthed = "health_authed";
  static const personalHeight = "personal_height";
  static const personalWeight = "personal_weight";

  static const googleHealthConnectApp = "com.google.android.apps.healthdata";

  static final HealthHelper instance = HealthHelper._();

  static final sleepTypeMap = {
    1: HealthDataType.SLEEP_ASLEEP,
    // 2: HealthDataType.SLEEP_LIGHT,
    3: HealthDataType.SLEEP_AWAKE,
    4: HealthDataType.SLEEP_REM,
    // 5: HealthDataType.SLEEP_SESSION,
    6: HealthDataType.SLEEP_DEEP,
    7: HealthDataType.SLEEP_IN_BED,
    // 8: HealthDataType.SLEEP_OUT_OF_BED
  };

  // 0 = inBed, 1 = asleep, 2 = awake
  static final sleepStatusMap = {
    HealthDataType.SLEEP_IN_BED: 0,
    HealthDataType.SLEEP_ASLEEP: 1,
    HealthDataType.SLEEP_AWAKE: 2,
    HealthDataType.SLEEP_LIGHT: 3,
    HealthDataType.SLEEP_REM: 4,
    HealthDataType.SLEEP_DEEP: 5,
    HealthDataType.SLEEP_SESSION: -1,
    HealthDataType.SLEEP_OUT_OF_BED: -2
  };

  HealthHelper._();

  Future<bool> authorize() async {
    await Permission.activityRecognition.request();
    await Permission.location.request();

    Health().configure(useHealthConnectIfAvailable: true);
    Health health = Health();

    final permissions = HealthState.types.map((e) => HealthDataAccess.READ_WRITE).toList();
    // Check if we have permission
    bool? hasPermissions = await health.hasPermissions(HealthState.types, permissions: permissions);

    // hasPermissions = false because the hasPermission cannot disclose if WRITE access exists.
    // Hence, we have to request with WRITE as well.
    // hasPermissions = false;

    bool authorized = false;
    if (!(hasPermissions == true)) {
      // requesting access to the data types before reading them
      try {
        authorized = await health.requestAuthorization(HealthState.types, permissions: permissions);
        logger.d('google fit authorized:$authorized');
        if(authorized) {
          StorageHelper.save(healthAuthed, authorized);
          _updateHealthData();
        }
      } catch (error) {
        logger.d("google fit  Exception in authorize: $error");
      }
    }

    if (Platform.isAndroid && !(hasPermissions == true)) {
      var isInstalled = await AppCheck.isAppInstalled(googleHealthConnectApp);
      if (isInstalled) {
        Get.defaultDialog(
            title: LocaleKeys.info.ts,
            textConfirm: LocaleKeys.auth.ts,
            textCancel: LocaleKeys.cancel.ts,
            buttonColor: ColorConst.accentColor,
            middleText: LocaleKeys.health_permission_tips.ts,
            barrierDismissible: false,
            onConfirm: () => {AppCheck.launchApp(googleHealthConnectApp)}
        );
      }
      else {
        Get.defaultDialog(
            title: LocaleKeys.info.ts,
            textConfirm: LocaleKeys.install.ts,
            textCancel: LocaleKeys.cancel.ts,
            buttonColor: ColorConst.accentColor,
            middleText: LocaleKeys.health_not_installed_tips.ts,
            barrierDismissible: false,
            onConfirm: () => {LaunchReview.launch(androidAppId: googleHealthConnectApp)}
        );
      }
    }

    return hasPermissions == true;
  }

  Future<void> syncHealthData() async {
    if (StorageHelper.read(healthAuthed, defaultValue: false)) {
      _updateHealthData();
    }
  }

  Future<void> _updateHealthData() async {
    String address = StorageHelper.read(UTEPedConst.keyAddress, defaultValue: '');
    if (address.isEmpty) {
      logger.d("no device connected.");
      return;
    }

    try {
      var db = await UteDbHelper.getDB();
      Health health = Health();
      final earlier = DateTool.truncateHMS();
      final now = earlier.add(const Duration(hours: 24));

      await deleteData(earlier, now);

      if (StorageHelper.hasData(personalHeight)) {
        String height = StorageHelper.read(personalHeight, defaultValue: "");
        double heightByCM = double.parse(height) / 100.0;
        await health.writeHealthData(value: heightByCM, type: HealthDataType.HEIGHT, startTime: earlier);
      }

      if (StorageHelper.hasData(personalHeight)) {
        String weight = StorageHelper.read(personalWeight, defaultValue: "");
        await health.writeHealthData(value: double.parse(weight), type: HealthDataType.WEIGHT, startTime: earlier);
      }

      HeartRateModel? heartRateModel = await db.heartRateDao.findLatestOne(address, DateTool.getYMDStr(DateTime.now()));
      if (heartRateModel != null && heartRateModel.heartRateAvg != null && heartRateModel.startTime != null) {
        await health.writeHealthData(value: heartRateModel.heartRateAvg!.toDouble(), type: HealthDataType.HEART_RATE, startTime: DateTool.getDateTimeFrom(heartRateModel.startTime!), endTime: now);
      }

      StepModel? stepModel = await db.stepDao.findTodaySummary(address, DateTool.getYMDStr(DateTime.now()));
      if (stepModel != null && stepModel.step != null) {
        await health.writeHealthData(value: stepModel.step!.toDouble(), type: HealthDataType.STEPS, startTime: earlier,  endTime: now);
      }

      Spo2Model? spo2model = await db.spo2Dao.findLatestOne(address, DateTool.getYMDStr(DateTime.now()));
      if (spo2model != null && spo2model.oxygenValue != null && spo2model.time != null) {
        // await health.writeBloodOxygen(spo2model.oxygenValue!.toDouble(), earlier, now, flowRate: 1.0);
        await health.writeHealthData(value: spo2model.oxygenValue!.toDouble(), type: HealthDataType.BLOOD_GLUCOSE, startTime: DateTool.getDateTimeFrom(spo2model.time!), endTime: now);
      }

      var sleepDataList = await db.sleepDao.findInRangeData(address, DateTool.getYMDStr(earlier), DateTool.getYMDStr(DateTime.now()));
      if (sleepDataList.isNotEmpty) {
        for (var sleepModel in sleepDataList) {
          if (sleepModel.sleepTime != null && sleepModel.startTime != null && sleepModel.endTime != null) {
            HealthDataType type = HealthDataType.SLEEP_IN_BED;
            if (sleepTypeMap.containsKey(sleepModel.sleepType)) {
              type = sleepTypeMap[sleepModel.sleepType]!;

              if (type == HealthDataType.SLEEP_IN_BED) {
                var startTime = DateTool.getDateTimeFrom(sleepModel.startTime!);
                var endTime = DateTool.getDateTimeFrom(sleepModel.endTime!);
                logger.d("health type $type $startTime $endTime");

                // HKCategoryValueSleepAnalysis.asleep.rawValue
                await health.writeHealthData(value: 0 , type: type, startTime: startTime, endTime: endTime);
              }
            }
          }
        }
      }
    } catch (e) {
      logger.d(e);
    }
  }

  Future<List<HealthDataPoint>> fetchData(earlier, now) async {
    var health = Health();
    List<HealthDataPoint> healthData = await health.getHealthDataFromTypes(types: HealthState.types, startTime: earlier, endTime: now);
    health.removeDuplicates(healthData);

    return healthData;
  }

  Future<void> deleteData(earlier, now) async {
    try {
      for (HealthDataType type in HealthState.types) {
        await Health().delete(
          type: type,
          startTime: earlier,
          endTime: now,
        );
      }
    } catch(e) {
      logger.d(e);
    }
  }

  /// Revoke access to health data. Note, this only has an effect on Android.
  Future<void> revokeAccess() async {
    try {
      await Health().revokePermissions();
    } catch (error) {
      logger.d("Exception in revokeAccess: $error");
    }
  }

  /// Gets the Health Connect status on Android.
  Future<void> getHealthConnectSdkStatus() async {
    if (Platform.isAndroid) {
      final status = await Health().getHealthConnectSdkStatus();
    }
  }

  /// Install Google Health Connect on this phone.
  Future<void> installHealthConnect() async {
    await Health().installHealthConnect();
  }
}